<HTML>
<BODY BGCOLOR="white">
<PRE>
<FONT color="green">001</FONT>    package org.hackystat.sensor.ant.svn;<a name="line.1"></a>
<FONT color="green">002</FONT>    <a name="line.2"></a>
<FONT color="green">003</FONT>    import java.util.ArrayList;<a name="line.3"></a>
<FONT color="green">004</FONT>    import java.util.Collection;<a name="line.4"></a>
<FONT color="green">005</FONT>    import java.util.Date;<a name="line.5"></a>
<FONT color="green">006</FONT>    import java.util.List;<a name="line.6"></a>
<FONT color="green">007</FONT>    import java.util.TreeMap;<a name="line.7"></a>
<FONT color="green">008</FONT>    import java.util.TreeSet;<a name="line.8"></a>
<FONT color="green">009</FONT>    <a name="line.9"></a>
<FONT color="green">010</FONT>    import org.tmatesoft.svn.core.SVNLogEntry;<a name="line.10"></a>
<FONT color="green">011</FONT>    import org.tmatesoft.svn.core.SVNLogEntryPath;<a name="line.11"></a>
<FONT color="green">012</FONT>    import org.tmatesoft.svn.core.SVNNodeKind;<a name="line.12"></a>
<FONT color="green">013</FONT>    import org.tmatesoft.svn.core.io.SVNFileRevision;<a name="line.13"></a>
<FONT color="green">014</FONT>    import org.tmatesoft.svn.core.io.SVNRepository;<a name="line.14"></a>
<FONT color="green">015</FONT>    <a name="line.15"></a>
<FONT color="green">016</FONT>    /**<a name="line.16"></a>
<FONT color="green">017</FONT>     * A commit record tracks all items that get modified during an SVN revision.<a name="line.17"></a>
<FONT color="green">018</FONT>     * Note that in SVN, versioning is performed on the entire repository instead of<a name="line.18"></a>
<FONT color="green">019</FONT>     * a single file.<a name="line.19"></a>
<FONT color="green">020</FONT>     * <a name="line.20"></a>
<FONT color="green">021</FONT>     * @author (Cedric) Qin ZHANG<a name="line.21"></a>
<FONT color="green">022</FONT>     */<a name="line.22"></a>
<FONT color="green">023</FONT>    public class CommitRecord {<a name="line.23"></a>
<FONT color="green">024</FONT>    <a name="line.24"></a>
<FONT color="green">025</FONT>      private SVNRepository svnRepository;<a name="line.25"></a>
<FONT color="green">026</FONT>      private SVNLogEntry svnLogEntry;<a name="line.26"></a>
<FONT color="green">027</FONT>      private List&lt;CommitRecordEntry&gt; commitRecordEntries = new ArrayList&lt;CommitRecordEntry&gt;();<a name="line.27"></a>
<FONT color="green">028</FONT>    <a name="line.28"></a>
<FONT color="green">029</FONT>      /**<a name="line.29"></a>
<FONT color="green">030</FONT>       * Constructs this instance.<a name="line.30"></a>
<FONT color="green">031</FONT>       * <a name="line.31"></a>
<FONT color="green">032</FONT>       * @param svnRepository The svn repository.<a name="line.32"></a>
<FONT color="green">033</FONT>       * @param svnLogEntry The svn log for a revision.<a name="line.33"></a>
<FONT color="green">034</FONT>       * <a name="line.34"></a>
<FONT color="green">035</FONT>       * @throws Exception If there is any error.<a name="line.35"></a>
<FONT color="green">036</FONT>       */<a name="line.36"></a>
<FONT color="green">037</FONT>      CommitRecord(SVNRepository svnRepository, SVNLogEntry svnLogEntry) throws Exception {<a name="line.37"></a>
<FONT color="green">038</FONT>        this.svnRepository = svnRepository;<a name="line.38"></a>
<FONT color="green">039</FONT>        this.svnLogEntry = svnLogEntry;<a name="line.39"></a>
<FONT color="green">040</FONT>        this.processSvnLogEntry();<a name="line.40"></a>
<FONT color="green">041</FONT>      }<a name="line.41"></a>
<FONT color="green">042</FONT>    <a name="line.42"></a>
<FONT color="green">043</FONT>      /**<a name="line.43"></a>
<FONT color="green">044</FONT>       * Process SVN log to extract information about which file/directory gets<a name="line.44"></a>
<FONT color="green">045</FONT>       * modified in this revision.<a name="line.45"></a>
<FONT color="green">046</FONT>       * <a name="line.46"></a>
<FONT color="green">047</FONT>       * @throws Exception If there is any error.<a name="line.47"></a>
<FONT color="green">048</FONT>       */<a name="line.48"></a>
<FONT color="green">049</FONT>      private void processSvnLogEntry() throws Exception {<a name="line.49"></a>
<FONT color="green">050</FONT>        long currentRevision = this.getRevision();<a name="line.50"></a>
<FONT color="green">051</FONT>    <a name="line.51"></a>
<FONT color="green">052</FONT>        // They are used to handle file or directory renaming.<a name="line.52"></a>
<FONT color="green">053</FONT>        TreeMap&lt;String, SVNLogEntryPath&gt; copyPaths = new TreeMap&lt;String, SVNLogEntryPath&gt;();<a name="line.53"></a>
<FONT color="green">054</FONT>        TreeSet&lt;String&gt; deletePaths = new TreeSet&lt;String&gt;();<a name="line.54"></a>
<FONT color="green">055</FONT>    <a name="line.55"></a>
<FONT color="green">056</FONT>        for (Object entryValue : this.svnLogEntry.getChangedPaths().values()) {<a name="line.56"></a>
<FONT color="green">057</FONT>          SVNLogEntryPath changedPath = (SVNLogEntryPath) entryValue;<a name="line.57"></a>
<FONT color="green">058</FONT>          String path = changedPath.getPath();<a name="line.58"></a>
<FONT color="green">059</FONT>          String copyPath = changedPath.getCopyPath(); // null if no copy path.<a name="line.59"></a>
<FONT color="green">060</FONT>    <a name="line.60"></a>
<FONT color="green">061</FONT>          char changeType = changedPath.getType();<a name="line.61"></a>
<FONT color="green">062</FONT>          if (changeType == 'A') {<a name="line.62"></a>
<FONT color="green">063</FONT>            // New file added, if copyPath == null.<a name="line.63"></a>
<FONT color="green">064</FONT>            // File rename, and possibly changed, then copyPath != null and there is<a name="line.64"></a>
<FONT color="green">065</FONT>            // a 'D' entry.<a name="line.65"></a>
<FONT color="green">066</FONT>            // (It seems there is always a 'D' entry')<a name="line.66"></a>
<FONT color="green">067</FONT>            // What about resurect of previously deleted file?<a name="line.67"></a>
<FONT color="green">068</FONT>            if (copyPath == null) { // newly created<a name="line.68"></a>
<FONT color="green">069</FONT>              this.commitRecordEntries.add(new CommitRecordEntry(this.svnRepository, null, -1,<a name="line.69"></a>
<FONT color="green">070</FONT>                  path, currentRevision));<a name="line.70"></a>
<FONT color="green">071</FONT>            }<a name="line.71"></a>
<FONT color="green">072</FONT>            else { // file rename, check for 'D'<a name="line.72"></a>
<FONT color="green">073</FONT>              copyPaths.put(copyPath, changedPath);<a name="line.73"></a>
<FONT color="green">074</FONT>            }<a name="line.74"></a>
<FONT color="green">075</FONT>          }<a name="line.75"></a>
<FONT color="green">076</FONT>          else if (changeType == 'D') {<a name="line.76"></a>
<FONT color="green">077</FONT>            // Delete an existing file, copyPath == null<a name="line.77"></a>
<FONT color="green">078</FONT>            // Note: Node kind is alway "none", we don't know it's a file or<a name="line.78"></a>
<FONT color="green">079</FONT>            // direcotry.<a name="line.79"></a>
<FONT color="green">080</FONT>            deletePaths.add(path); // process later, some are deletion, some are<a name="line.80"></a>
<FONT color="green">081</FONT>            // renaming.<a name="line.81"></a>
<FONT color="green">082</FONT>          }<a name="line.82"></a>
<FONT color="green">083</FONT>          else if (changeType == 'M') {<a name="line.83"></a>
<FONT color="green">084</FONT>            // Modify existing file, copyPath == null<a name="line.84"></a>
<FONT color="green">085</FONT>            // Note, if you rename a top directory, all files in the directory will<a name="line.85"></a>
<FONT color="green">086</FONT>            // be moified<a name="line.86"></a>
<FONT color="green">087</FONT>            // with copyPath == null, even if there is no change in those files.<a name="line.87"></a>
<FONT color="green">088</FONT>            // Find out the file name in the previous revision, which might have<a name="line.88"></a>
<FONT color="green">089</FONT>            // been changed.<a name="line.89"></a>
<FONT color="green">090</FONT>    <a name="line.90"></a>
<FONT color="green">091</FONT>            if (SVNNodeKind.FILE == this.svnRepository.checkPath(path, currentRevision)) {<a name="line.91"></a>
<FONT color="green">092</FONT>              Collection&lt;?&gt; revisions = this.svnRepository.getFileRevisions(path, null, 1,<a name="line.92"></a>
<FONT color="green">093</FONT>                  currentRevision);<a name="line.93"></a>
<FONT color="green">094</FONT>              // we should alway find at least one, since change type is 'M'.<a name="line.94"></a>
<FONT color="green">095</FONT>              if (revisions == null || revisions.isEmpty()) {<a name="line.95"></a>
<FONT color="green">096</FONT>                throw new RuntimeException("Inconsistent SVN record. Corrupted SVN repository?");<a name="line.96"></a>
<FONT color="green">097</FONT>              }<a name="line.97"></a>
<FONT color="green">098</FONT>              long thePrevRevisionNumber = -1;<a name="line.98"></a>
<FONT color="green">099</FONT>              SVNFileRevision thePrevFileRevision = null;<a name="line.99"></a>
<FONT color="green">100</FONT>              for (Object revision : revisions) {<a name="line.100"></a>
<FONT color="green">101</FONT>                SVNFileRevision curRevision = (SVNFileRevision) revision;<a name="line.101"></a>
<FONT color="green">102</FONT>                long curRevisionNumber = curRevision.getRevision();<a name="line.102"></a>
<FONT color="green">103</FONT>                if (thePrevRevisionNumber &lt; curRevisionNumber<a name="line.103"></a>
<FONT color="green">104</FONT>                    &amp;&amp; curRevisionNumber &lt; currentRevision) {<a name="line.104"></a>
<FONT color="green">105</FONT>                  thePrevRevisionNumber = curRevisionNumber;<a name="line.105"></a>
<FONT color="green">106</FONT>                  thePrevFileRevision = curRevision;<a name="line.106"></a>
<FONT color="green">107</FONT>                }<a name="line.107"></a>
<FONT color="green">108</FONT>              }<a name="line.108"></a>
<FONT color="green">109</FONT>              if (thePrevFileRevision == null) {<a name="line.109"></a>
<FONT color="green">110</FONT>                throw new RuntimeException("Assertion Failed.");<a name="line.110"></a>
<FONT color="green">111</FONT>              }<a name="line.111"></a>
<FONT color="green">112</FONT>              this.commitRecordEntries.add(new CommitRecordEntry(this.svnRepository,<a name="line.112"></a>
<FONT color="green">113</FONT>                  thePrevFileRevision.getPath(), thePrevFileRevision.getRevision(), path,<a name="line.113"></a>
<FONT color="green">114</FONT>                  currentRevision));<a name="line.114"></a>
<FONT color="green">115</FONT>            }<a name="line.115"></a>
<FONT color="green">116</FONT>          }<a name="line.116"></a>
<FONT color="green">117</FONT>          else if (changeType == 'R') {<a name="line.117"></a>
<FONT color="green">118</FONT>            // Replace, the object is first deleted, and another with the same name<a name="line.118"></a>
<FONT color="green">119</FONT>            // added,<a name="line.119"></a>
<FONT color="green">120</FONT>            // all within single revision (Note: I CANNOT produce this in SVN).<a name="line.120"></a>
<FONT color="green">121</FONT>            // So, it's actually a deletion plus an addition.<a name="line.121"></a>
<FONT color="green">122</FONT>            this.commitRecordEntries.add(new CommitRecordEntry(this.svnRepository, path,<a name="line.122"></a>
<FONT color="green">123</FONT>                currentRevision - 1, null, currentRevision));<a name="line.123"></a>
<FONT color="green">124</FONT>            this.commitRecordEntries.add(new CommitRecordEntry(this.svnRepository, null, -1, path,<a name="line.124"></a>
<FONT color="green">125</FONT>                currentRevision));<a name="line.125"></a>
<FONT color="green">126</FONT>          }<a name="line.126"></a>
<FONT color="green">127</FONT>          else {<a name="line.127"></a>
<FONT color="green">128</FONT>            throw new RuntimeException("Unknown SVN change type.");<a name="line.128"></a>
<FONT color="green">129</FONT>          }<a name="line.129"></a>
<FONT color="green">130</FONT>        }<a name="line.130"></a>
<FONT color="green">131</FONT>    <a name="line.131"></a>
<FONT color="green">132</FONT>        // handle delete and file rename<a name="line.132"></a>
<FONT color="green">133</FONT>        for (String deletePath : deletePaths) {<a name="line.133"></a>
<FONT color="green">134</FONT>          SVNLogEntryPath addLogEntryPath = copyPaths.get(deletePath);<a name="line.134"></a>
<FONT color="green">135</FONT>          if (addLogEntryPath == null) { // true delete<a name="line.135"></a>
<FONT color="green">136</FONT>            this.commitRecordEntries.add(new CommitRecordEntry(this.svnRepository, deletePath,<a name="line.136"></a>
<FONT color="green">137</FONT>                currentRevision - 1, null, currentRevision));<a name="line.137"></a>
<FONT color="green">138</FONT>          }<a name="line.138"></a>
<FONT color="green">139</FONT>          else { // rename<a name="line.139"></a>
<FONT color="green">140</FONT>            this.commitRecordEntries.add(new CommitRecordEntry(this.svnRepository, addLogEntryPath<a name="line.140"></a>
<FONT color="green">141</FONT>                .getCopyPath(), addLogEntryPath.getCopyRevision(), addLogEntryPath.getPath(),<a name="line.141"></a>
<FONT color="green">142</FONT>                currentRevision));<a name="line.142"></a>
<FONT color="green">143</FONT>          }<a name="line.143"></a>
<FONT color="green">144</FONT>        }<a name="line.144"></a>
<FONT color="green">145</FONT>      }<a name="line.145"></a>
<FONT color="green">146</FONT>    <a name="line.146"></a>
<FONT color="green">147</FONT>      /**<a name="line.147"></a>
<FONT color="green">148</FONT>       * Gets this revision number.<a name="line.148"></a>
<FONT color="green">149</FONT>       * <a name="line.149"></a>
<FONT color="green">150</FONT>       * @return The revision number.<a name="line.150"></a>
<FONT color="green">151</FONT>       */<a name="line.151"></a>
<FONT color="green">152</FONT>      public final long getRevision() {<a name="line.152"></a>
<FONT color="green">153</FONT>        return this.svnLogEntry.getRevision();<a name="line.153"></a>
<FONT color="green">154</FONT>      }<a name="line.154"></a>
<FONT color="green">155</FONT>    <a name="line.155"></a>
<FONT color="green">156</FONT>      /**<a name="line.156"></a>
<FONT color="green">157</FONT>       * Gets the author who made the commit.<a name="line.157"></a>
<FONT color="green">158</FONT>       * <a name="line.158"></a>
<FONT color="green">159</FONT>       * @return The authoer.<a name="line.159"></a>
<FONT color="green">160</FONT>       */<a name="line.160"></a>
<FONT color="green">161</FONT>      public String getAuthor() {<a name="line.161"></a>
<FONT color="green">162</FONT>        return this.svnLogEntry.getAuthor();<a name="line.162"></a>
<FONT color="green">163</FONT>      }<a name="line.163"></a>
<FONT color="green">164</FONT>    <a name="line.164"></a>
<FONT color="green">165</FONT>      /**<a name="line.165"></a>
<FONT color="green">166</FONT>       * Gets the commit time.<a name="line.166"></a>
<FONT color="green">167</FONT>       * <a name="line.167"></a>
<FONT color="green">168</FONT>       * @return The commit time.<a name="line.168"></a>
<FONT color="green">169</FONT>       */<a name="line.169"></a>
<FONT color="green">170</FONT>      public Date getCommitTime() {<a name="line.170"></a>
<FONT color="green">171</FONT>        return this.svnLogEntry.getDate();<a name="line.171"></a>
<FONT color="green">172</FONT>      }<a name="line.172"></a>
<FONT color="green">173</FONT>    <a name="line.173"></a>
<FONT color="green">174</FONT>      /**<a name="line.174"></a>
<FONT color="green">175</FONT>       * Gets the commit log message.<a name="line.175"></a>
<FONT color="green">176</FONT>       * <a name="line.176"></a>
<FONT color="green">177</FONT>       * @return The commit log message.<a name="line.177"></a>
<FONT color="green">178</FONT>       */<a name="line.178"></a>
<FONT color="green">179</FONT>      public String getMessage() {<a name="line.179"></a>
<FONT color="green">180</FONT>        return this.svnLogEntry.getMessage();<a name="line.180"></a>
<FONT color="green">181</FONT>      }<a name="line.181"></a>
<FONT color="green">182</FONT>    <a name="line.182"></a>
<FONT color="green">183</FONT>      /**<a name="line.183"></a>
<FONT color="green">184</FONT>       * Returns the string representation of this record.<a name="line.184"></a>
<FONT color="green">185</FONT>       * @return the string representation.<a name="line.185"></a>
<FONT color="green">186</FONT>       */<a name="line.186"></a>
<FONT color="green">187</FONT>      @Override<a name="line.187"></a>
<FONT color="green">188</FONT>      public String toString() {<a name="line.188"></a>
<FONT color="green">189</FONT>        return "Author=" + this.getAuthor() + ", Message=" + this.getMessage() + ", CommitTime="<a name="line.189"></a>
<FONT color="green">190</FONT>            + this.getCommitTime();<a name="line.190"></a>
<FONT color="green">191</FONT>      }<a name="line.191"></a>
<FONT color="green">192</FONT>    <a name="line.192"></a>
<FONT color="green">193</FONT>      /**<a name="line.193"></a>
<FONT color="green">194</FONT>       * Gets all changed items in this revision.<a name="line.194"></a>
<FONT color="green">195</FONT>       * <a name="line.195"></a>
<FONT color="green">196</FONT>       * @return A collection of &lt;code&gt;CommitRecordEntry&lt;/code&gt; instances.<a name="line.196"></a>
<FONT color="green">197</FONT>       */<a name="line.197"></a>
<FONT color="green">198</FONT>      public Collection&lt;CommitRecordEntry&gt; getCommitRecordEntries() {<a name="line.198"></a>
<FONT color="green">199</FONT>        return this.commitRecordEntries;<a name="line.199"></a>
<FONT color="green">200</FONT>      }<a name="line.200"></a>
<FONT color="green">201</FONT>    }<a name="line.201"></a>




























































</PRE>
</BODY>
</HTML>
